package seminar;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;

/**
 * A child containing a list of semesters
 * @author Johannes Degler
 *
 */
public class Child implements Comparable<Child>{

	private static final Logger log = Logger.getLogger(Child.class);

	private static final double MUTATION_MODIFIER = 0.32; //Lower values == less mutations

	private static int childCount = 0;

	private final Map<String, Room> rooms = new TreeMap<String, Room>(); //Map so getting rooms by name is faster

	private final int childNumber = Child.childCount++;

	private final Map<String, Semester> semesters; //Map so getting semesters by name is faster

	private Integer averagePoints = null;

	/**
	 * Create new, initial generation child
	 */
	public Child(){
		Child.log.debug("New initial child: " + this);

		//Init semester list
		this.semesters = this.createSemesterMap();

		this.initRooms();
	}

	/**
	 * Create a new child based on two other children as parents
	 * @param parent1 First parent
	 * @param parent2 Second parent
	 * @throws DistributionException Thrown if distributing lectures fails (Shouldn't happen at this point)
	 */
	public Child(final Child parent1, final Child parent2) throws DistributionException{
		Child.log.debug("New child " + this + " based on " + parent1 + " and " + parent2);

		this.semesters = new TreeMap<String, Semester>();

		//Init available rooms
		this.initRooms();

		//Copy semesters
		this.copySemesters(parent1, parent2);

		//Mutate
		this.mutate();
	}

	/**
	 * Calculate points
	 */
	private void calcPoints() {
		Child.log.debug("Calculating average points for child " + this);
		long points = 0;
		for(final Semester semester : this.semesters.values())
			points += semester.getPoints();

		this.averagePoints = (int)(points / this.semesters.size());
	}

	@Override
	public int compareTo(final Child o) {
		return o.getPoints() - this.getPoints();
	}

	/**
	 * Copy semesters from the parents
	 * @param parent1 First parent
	 * @param parent2 Second parent
	 * @throws DistributionException Thrown if distributing lectures fails (Shouldn't happen at this point)
	 */
	private void copySemesters(final Child parent1, final Child parent2) throws DistributionException {
		for(final Semester parentSemester : parent1.getSemesters().values())
			if(Math.random() < 0.5) {
				final Semester semester = new Semester(parentSemester, this);
				this.semesters.put(semester.getName(), semester);
			} else {
				final Semester semester = new Semester(parent2.getSemesterByName(parentSemester.getName()), this);
				this.semesters.put(semester.getName(), semester);
			}
	}

	private Map<String, Semester> createSemesterMap(){
		final Map<String, Semester> map = new TreeMap<String, Semester>();

		for(final Semester semester : Main.createSemesterSet(this))
			map.put(semester.getName(), semester);

		return map;
	}

	/**
	 * Evaluate this child's semesters
	 */
	public void evaluate(){
		Child.log.debug("Evaluating child " + this);
		for(final Semester s : this.semesters.values())
			s.evaluate();
	}

	/**
	 * Get this child's number/id
	 * @return Number
	 */
	public int getChildNumber() {
		return this.childNumber;
	}

	/**
	 * Get a random, free room
	 * @param events Current lectures list the room must not colide with
	 * @return A random free room
	 * @throws DistributionException Thrown if no free room is left
	 */
	public Room getFreeRoom(final LectureEvent events[][]) throws DistributionException{
		Child.log.debug("Getting free room for child " + this);
		final List<Room> possibleRooms  = new ArrayList<Room>();
		//Get free rooms left for this events
		for(final Room r : this.rooms.values())
			if(r.isFreePointLeft(events))
				possibleRooms.add(r);

		if(possibleRooms.isEmpty())
			throw new DistributionException("No free rooms left");
		else
			return possibleRooms.get((int)(Math.random() * possibleRooms.size()));

	}

	/**
	 * Get this child's points (from evaluation)
	 * @return
	 */
	public int getPoints(){
		if(this.averagePoints == null)
			this.calcPoints();
		return this.averagePoints;
	}

	/**
	 * Get a room by its name
	 * @param name Room's name
	 * @return Room
	 */
	public Room getRoomByName(final String name){
		return this.rooms.get(name);
	}

	/**
	 * Get rooms list
	 * @return Rooms list
	 */
	public Map<String, Room> getRooms() {
		return this.rooms;
	}

	/**
	 * Get a semester by its name
	 * @param name Semester's name
	 * @return Semester
	 */
	public Semester getSemesterByName(final String name){
		return this.semesters.get(name);
	}

	/**
	 * Get semesters list
	 * @return Semesters list
	 */
	public Map<String, Semester> getSemesters() {
		return this.semesters;
	}

	/**
	 * Initial distribution of all children's lectures
	 * @throws DistributionException Thrown if distributing lectures fails
	 */
	public void initialDistribute() throws DistributionException{
		Child.log.debug("Initial distribution for child " + this);
		for(final Semester s : this.semesters.values())
			s.distribute();
	}

	/**
	 * Initialize rooms list
	 */
	private void initRooms() {
		for(final Room r : Room.getRoomList())
			this.rooms.put(r.getDescription(), r);
	}

	/**
	 * Mutate this child
	 * @throws DistributionException Thrown if redistributing lectures fails (Shouldn't happen at this point)
	 */
	private void mutate() throws DistributionException {
		for(final Semester semester : this.semesters.values())
			if(Math.random() < Child.MUTATION_MODIFIER)
				semester.mutate();
	}

	@Override
	public String toString() {
		return "Child " + this.childNumber;
	}
}
